package ci.web.router;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONAware;
import com.alibaba.fastjson.JSONObject;

import ci.web.codec.FileItem;
import ci.web.core.CiContext;
import ci.web.core.CiRequest;
import ci.web.core.CiResponse;
import ci.web.util.CiClassLoader;
import ci.web.util.JxHelper;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.Modifier;

/**
 * 代理调用基类
 * @author zhh
 */
public abstract class CiHandlerCallProxy {

	protected CiContext context;
	public CiHandlerCallProxy(CiContext ctx) {
		this.context = ctx;
	}
	public byte[] body(){
		return context.body();
	}
//	public String bodyString(){
//		return new String(context.body());
//	}
//	public JSONObject request(){
//		return context.params();
//	}
//	public JSONObject get(){
//		return context.get();
//	}
//	public JSONObject post(){
//		return context.post();
//	}
	
	public CiResponse out(){
		return context.out();
	}
	public CiRequest in(){
		return context.in();
	}
	
	public Object getCast(String name, Class<?> clazz){
		return com.alibaba.fastjson.util.TypeUtils.cast(context.params().get(name), clazz, com.alibaba.fastjson.parser.ParserConfig.getGlobalInstance());
	}
	
	public JSONObject request(String name){
		if(name.charAt(0)=='$'){
			if(name.equalsIgnoreCase("$post")){
				return context.post();
			}
			if(name.equalsIgnoreCase("$get")){
				return context.get();
			}
		}
		return context.params();
	}
	public File getFile(int idx){
		if(context.files().isEmpty() || context.files().size()<=idx){
			return null;
		}
		return context.files().get(idx).getFile();
	}
	public File getFile(String name){
		FileItem item = context.getFile(name);
		return item==null ? null:item.getFile();
	}
	public String[] getStringArray(String names){
		JSONArray arr = context.params().getJSONArray(names);
		return arr==null ? null:arr.toArray(new String[arr.size()]);
	}
	public JSONArray getArray(String names){
		return context.params().getJSONArray(names);
	}
	public String getString(String name){
		if(name.charAt(0)=='$'){
			if(name.equals("$uri")){
				return context.uri();
			}else if(name.equals("$path")){
				return context.path();
			}else if(name.equals("$body")){
				return new String(context.body());
			}
		}
		return context.params().getString(name);
	}
	public Number getNumber(String name){
		return context.params().getDouble(name);
	}
	public Date getDate(String name){
		return context.params().getDate(name);
	}
	public boolean getBooleanValue(String name){
		return context.params().getBooleanValue(name);
	}
	public float getFloatValue(String name){
		return context.params().getFloatValue(name);
	}
	public double getDoubleValue(String name){
		return context.params().getDoubleValue(name);
	}
	public long getLongValue(String name){
		return context.params().getLongValue(name);
	}
	public int getIntValue(String name){
		return context.params().getIntValue(name);
	}
	public short getShortValue(String name){
		return context.params().getShortValue(name);
	}
	public byte getByteValue(String name){
		return context.params().getByteValue(name);
	}
	public Byte getByte(String name){
		return context.params().getByte(name);
	}
	public Short getShort(String name){
		return context.params().getShort(name);
	}
	public Integer getInteger(String name){
		return context.params().getInteger(name);
	}
	public Long getLong(String name){
		return context.params().getLong(name);
	}
	public Float getFloat(String name){
		return context.params().getFloat(name);
	}
	public Double getDouble(String name){
		return context.params().getDouble(name);
	}
	public Boolean getBoolean(String name){
		return context.params().getBoolean(name);
	}
	
	private static InternalLogger logger = InternalLoggerFactory.getInstance("CiHandlerCallProxy");
	
	public static Constructor<?> make(Executable method){
		ClassLoader loader = method.getDeclaringClass().getClassLoader();
		ClassPool pool = null;
		if(loader instanceof CiClassLoader){
			pool = ((CiClassLoader)loader).getClassPool();
		}else{
			pool = ClassPool.getDefault();
		}
		try{
			return make(pool, method);
		}catch(Exception e){
			logger.error("make-call-proxy", e);
			return null;
		}
	}
	public static Constructor<?> make(ClassPool pool, Executable method) throws Exception{
		String className = method.getDeclaringClass().getName()+"_"+method.getName()+"_proxy";
		CtClass clazzImp = pool.makeClass(className);
		clazzImp.setSuperclass(pool.get(CiHandlerCallProxy.class.getName()));
		CtConstructor constructor = new CtConstructor(new CtClass[] { pool.get(CiContext.class.getName()) }, clazzImp);
		constructor.setModifiers(Modifier.PUBLIC);
		Class<?>[] types = method.getParameterTypes();
		String[] names = JxHelper.getParamNames(pool, method);
		ArrayList<String> list = new ArrayList<>(types.length);
		for(int i=0; i<types.length; i++){
			Class<?> clazz = types[i];
			String name = names[i];
			if(clazz.isPrimitive()){
				list.add("get"+clazz.getName().substring(0,1).toUpperCase()+clazz.getName().substring(1)+"Value(\""+name+"\")");
			}
			else if(Number.class.isAssignableFrom(clazz)){
				list.add("get"+clazz.getSimpleName()+"(\""+name+"\")");
			}
			else if(clazz==String.class){
				list.add("getString(\""+name+"\")");
			}
			else if(clazz==File.class){
    			if(isIndexName(name)){
    				int file_idx = Integer.parseInt(name);
    				list.add("getFile("+file_idx+")");
    			}else{
    				list.add("getFile(\""+name+"\")");
    			}
    		}
			else if(CiContext.class.isAssignableFrom(clazz)) {
				list.add("$1");
			}else if(CiResponse.class.isAssignableFrom(clazz)){
				list.add("out()");
			}else if(CiRequest.class.isAssignableFrom(clazz)){
				list.add("in()");
			}else if(clazz==byte[].class){//直接获取post-body字节数组
				list.add("body()");
			}
			else if(clazz==JSONObject.class){//直接使用 get/post/request作为参数
				list.add("request(\""+name+"\")");
			}
			else if(clazz==JSONArray.class){//http-数组参数
				list.add("getArray(\""+name+"\")");
			}else if(clazz==String[].class){//http-数组参数
				list.add("getStringArray(\""+name+"\")");
			}else{
				list.add("("+clazz.getName()+")getCast(\""+name+"\", "+clazz.getName()+".class)");
			}
		}
		StringBuilder ps = new StringBuilder();
		for (int i = 0; i < list.size(); i++) {
			ps.append(list.get(i));
			if ((i + 1) < list.size()) {
				ps.append(',').append(' ');
			}
		}
		StringBuilder sb = new StringBuilder();
		sb.append("{\n").append("  super($1);\n");
		if(method instanceof Method){
			Class<?> retType = ((Method)method).getReturnType();
			if(retType!=void.class){
				sb.append("  "+retType.getName()+" ret = ");
			}else{
				sb.append("  ");
			}
			if(Modifier.isStatic(method.getModifiers())){
				sb.append(method.getDeclaringClass().getName())
				.append('.')
				.append('(').append(ps).append(");\n");
			}else{
				sb.append("new ").append(method.getDeclaringClass().getName())
				.append("().").append(method.getName())
				.append('(').append(ps).append(");\n");
			}
			if(retType!=void.class){
				sb.append("  if($1.isWroteBody()==false){\n");
				if(JSONAware.class.isAssignableFrom(retType)){
					sb.append("    $1.send((com.alibaba.fastjson.JSONAware)ret);\n");
    			}else if(byte[].class==retType){
    				sb.append("    $1.send(ret);\n");
    			}else if(File.class==retType){
    				sb.append("    $1.send(ret);\n");
    			}else{
    				sb.append("    $1.send(String.valueOf(ret));\n");
    			}
				sb.append("  }\n");
			}
		}else{
			sb.append("new ").append(method.getDeclaringClass().getName())
			.append('(').append(ps).append(");\n");
		}
		sb.append('}');
		String body = sb.toString();
//		System.out.println(body);
		constructor.setBody(body);
		clazzImp.addConstructor(constructor);
		byte[] byteCode = clazzImp.toBytecode();
		clazzImp.detach();
		Method defineMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, Integer.TYPE, Integer.TYPE });
		defineMethod.setAccessible(true);
		Class<?> generated = (Class<?>)defineMethod.invoke(method.getDeclaringClass().getClassLoader(), new Object[] {className, byteCode, Integer.valueOf(0), Integer.valueOf(byteCode.length) });
		Constructor<?> cp = generated.getConstructor(CiContext.class);
		return cp;
	}
	private static boolean isIndexName(String s){
		return s.length()==1 && s.charAt(0)>='0'||s.charAt(0)<='9';
	}
}
